<?php

/**
 * Ferramenta de linha de comando, assitente para tarefas chatas como gerar models,
 * fazer verificações, exportar diferencas entre versoes, popular dados em tabelas
 * e outras tarefas indignas de serem feitas por um humano.
 *
 * 
 * 
 * @author Níssius Ribas
 */
require_once APPPATH . "third_party/ColorCLI.php";

class Ally extends CI_Controller {

    public $qtdColunasUsar = 4;
    public $linhasPreview = 20; // quantas linhas exibir nos previews

    /* Lista de models que nao queremos que aparecam na lista para por acidente serem re-geradas */
    public $preventOverwriteModels = array('grocery_crud_model',
        'nt_ally',
        'nt_global_cidades',
        'nt_global_estados',
        'nt_global_facebook_feeds',
        'nt_global_favicon',
        'nt_global_google_analytics',
        'nt_global_graphs',
        'nt_global_idiomas',
        'nt_global_instagram_feeds',
        'nt_global_logs',
        'nt_global_mtemplates',
        'nt_global_paises',
        'nt_global_parametros',
        'nt_global_seo',
        'nt_global_setores',
        'nt_global_setores_usuarios',
        'nt_grocery',
        'nt_manager_menus',
        'nt_manager_menus_papeis',
        'nt_manager_metodos',
        'nt_manager_metodos_menus',
        'nt_manager_metodos_papeis',
        'nt_manager_origens',
        'nt_manager_origens_papeis',
        'nt_manager_papeis',
        'nt_manager_permissoes',
        'nt_manager_testmode',
        'nt_manager_upload',
        'nt_manager_usuarios',
        'nt_site_twitter',
        'nt_gd_cities',
        'nt_gd_configurations',
        'nt_gd_countries',
        'nt_gd_first_names',
        'nt_gd_last_names',
        'nt_gd_regions',
        'nt_gd_sessions',
        'nt_gd_settings',
        'nt_gd_user_accounts',
        'nt_global_sessions');
    
    public $preventOverwriteControllersManager = array('m_cidades',
        'm_favicon',
        'm_instagram_feeds',
        'm_menus',
        'm_ops',
        'm_parametros',
        'm_tools',
        'm_usuarios',
        'm_contatos',
        'm_graphs',
        'm_internal',
        'm_metodos',
        'm_origens',
        'm_seo',
        'm_twitter',
        'm_estados',
        'm_idiomas',
        'm_login',
        'm_mtemplates',
        'm_paises',
        'm_setores',
        'm_upload',
        'm_facebook_feeds',
        'm_index.php',
        'm_logs.php',
        'm_mysqldbdump',
        'm_papeis',
        'm_testmode',
        'm_uploadup');

    public function __construct() {
        parent::__construct();
        $this->load->library('table');
        $this->load->model("nt_ally");

        if (isset($_SERVER['REMOTE_ADDR'])) {
            die("Ally pode apenas ser executado pela interface CLI");
        }

        if ($this->db->dbdriver != 'mysql') {
            die("\nSorry. Eu só sei trabalhar com Mysql... Vai ter que criar as coisas no braço.\n");
        }
    }

    public function index() {

        echo ColorCLI::getColoredString("\n\n-------------- Don't panic, and carry a towel. Ally is here --------------\n\n", 'green');



        echo ColorCLI::getColoredString("1 - Gerar/Atualizar models\n", 'white');
        echo ColorCLI::getColoredString("2 - Atualizar fake_autocomplete.php\n", 'white');
        echo ColorCLI::getColoredString("3 - Criar controller do manager\n", 'white');
        echo ColorCLI::getColoredString("4 - Exportar/Publicar site\n", 'white');
        echo ColorCLI::getColoredString("0 - Sair\n\n", 'white');


        echo ColorCLI::getColoredString("O que fazer? ", 'white');
        $choice = clir();

        switch ($choice) {
            case 1:
                $this->initGerarModel();
                break;
            case 2:
                $this->initAtualizarFake();
                break;
            case 3:
                $this->initGerarController();
                break;

            case 4:
                $this->iniSvnExport();
                break;

            case 0:
                echo ColorCLI::getColoredString("\nAté mais! E obrigado pelos peixes! \n\n", 'green');
                exit();
                break;
            default:
                $this->index(); // volta pro index
        }
    }

    /**
     * Inicializa, função de entrada ao usuário escolher o menu
     * de geração de models
     */
    private function initGerarModel() {

        echo ColorCLI::getColoredString("\n----> Gerar MODEL's \n");
        echo ColorCLI::getColoredString("\nInforme a tabela em questão\n\n", 'white');

        $rs = $this->nt_ally->getTableList();

        $usefull = array();

        foreach ($rs as $row) {
            foreach ($row as $innerRow)
                if (!in_array($innerRow, $this->preventOverwriteModels)) {
                    $usefull[] = $innerRow;
                }
        }

        $break = 0;
        foreach ($usefull as $key => $exibir) {
            $break++;

            // 40 caracteres cada coluna, simula tipo um tab, completa com espaço para ficar alinhado
            $exibir = str_pad($key . " => " . $exibir, 40);

            echo ColorCLI::getColoredString($exibir, 'white');

            if ($break == $this->qtdColunasUsar) { // quantas colunas, em que deve quebrar linha.
                echo("\n");
                $break = 0;
            }
        }

        echo("\nFormatos de entrada: 12 ou 1,2,8,10 ou 10-15");
        echo ColorCLI::getColoredString("\nCriar para a tabela ('b' para voltar) : ", 'white');
        $index = clir();

        // esc volta para o inicio. Nao da para prender o usuário
        if ($index == 'b')
            $this->index();


        // tenta fazer por virgula, lista enumerada de models que quero gerar
        $porVirgula = explode(',', $index);

        if (count($porVirgula) > 1):

            foreach ($porVirgula as $ichooseYou) {
                $escolhida = (isset($usefull[$ichooseYou])) ? $usefull[$ichooseYou] : null;


                if ($escolhida) {

                    $this->ModelsPreCheck($escolhida);
                } else {
                    echo ColorCLI::getColoredString("\nTabela inválida. Escolha novamente.\n", 'red');
                    $this->initGerarModel();
                }
            }

            $this->initAtualizarFake();

        endif;


        // tenta fazer por range de index da model que deve gerar
        $porRange = explode("-", $index);
        if (count($porRange) == 2) {

            for ($magaiver = $porRange[0]; $magaiver <= $porRange[1]; $magaiver++) {

                $escolhida = (isset($usefull[$magaiver])) ? $usefull[$magaiver] : null;


                if ($escolhida) {

                    $this->ModelsPreCheck($escolhida);
                } else {
                    echo ColorCLI::getColoredString("\nTabela inválida. Escolha novamente.\n", 'red');
                    $this->initGerarModel();
                }
            }

            $this->initAtualizarFake();
        }



        $escolhida = (isset($usefull[$index])) ? $usefull[$index] : null;

        if ($escolhida) {

            $this->ModelsPreCheck($escolhida);
            // ao criar uma model ou modificar, ja ajusta o fake
            $this->initAtualizarFake();
        } else {
            echo ColorCLI::getColoredString("\nTabela inválida. Escolha novamente.\n", 'red');
            $this->initGerarModel();
        }
    }

    /**
     * Usado para dar preview em um arquivo texto.
     * 
     * @param type $fullPath
     */
    private function filePreview($fullPath) {

        echo ColorCLI::getColoredString("\n---------------------------Preview-----------------------------------\n", 'green');
        // se o arquivo ja existe da um preview dele
        $content = file($fullPath);
        for ($i = 0; $i <= $this->linhasPreview; $i++) {
            echo ColorCLI::getColoredString((isset($content[$i]) ? $content[$i] : ""), 'red');
        }

        echo ColorCLI::getColoredString("\n----------------------------------------------------------------------\n", 'green');
    }

    /**
     * Ao entrar na geração de models, primeiro testa
     * se deve gerar a tal model ou não.
     * 
     * @param string $table_name
     */
    private function ModelsPreCheck($table_name) {

        echo ColorCLI::getColoredString("\nModel para: $table_name \n", 'white');

        $rs = $this->nt_ally->describe($table_name);

        $path = $this->config->item("local_disk_url") . "application/models/" . $table_name . ".php";


        if (file_exists($path)) {

            echo ColorCLI::getColoredString("\nO arquivo já existe! Primeiras {$this->linhasPreview} linhas abaixo:\n", 'red');

            $this->filePreview($path);

            echo ColorCLI::getColoredString("\nDeseja sobrescrever (s/n) ? ", 'white');

            $decision = clir();

            if ($decision == 's')
                $this->ModelCheckFields($table_name);
            else {
                $this->initGerarModel();
            }
        } else {
            $this->ModelCheckFields($table_name);
        }
    }

    /**
     * Gera a model em disco, buscando campo a campo as suas propriedades
     * 
     * @param type $tabela
     */
    private function ModelCheckFields($tabela) {
        $rs = $this->nt_ally->describe($tabela);
        $rs2 = $this->nt_ally->describeBetter($tabela);

        $campos_unidos = '';

        foreach ($rs2 as $campo) {
            $regra = '';
            if ($campo['obrigatorio'] != '') {
                if ($campo['tamanho_maximo'] != '') {
                    $regra = $campo['obrigatorio'] . '|max_length[' . $campo['tamanho_maximo'] . ']';
                } else {
                    $regra = $campo['obrigatorio'];
                }
            } else {
                if ($campo['tamanho_maximo'] != '') {
                    $regra = 'max_length[' . $campo['tamanho_maximo'] . ']';
                }
            }

            $campos_unidos .= "\tarray('field' => '$campo[column_name]', 'label' => '$campo[label]', 'rules' => '$regra'),\n";
        }

        $campos_unidos = substr($campos_unidos, 0, -2);

        $phpCode = "<?php
            
/**
 * Modelo de dados para $tabela. Descreva a função desta model.
 * 
 * @author Seu Nome Aqui <seu-email@noiatec.com.br>, <seu-outro-email@pessoal.com>
 */
class $tabela extends NT_Model {
    
    private \$validation = array(
$campos_unidos
    );
    
    /**
     * Devolve o array com as validações para esta model
     * 
     * @return array com as regras de validação desta model
     */
    public function getRules(){
        return \$this->validation;
    }
    
}";
        $file = fopen($this->config->item("local_disk_url") . 'application/models/' . $tabela . ".php", "w+");
        if (fwrite($file, stripslashes($phpCode))) {
            fclose($file);
            echo ColorCLI::getColoredString("\nModel $tabela criada/atualizada/sobrescrita. Fazer outra? \n", 'white');
        }
    }

    private function initAtualizarFake() {

        echo ColorCLI::getColoredString("\nAtualizando o fake ... \n", 'white');

        $path = $this->config->item("local_disk_url") . "application/models/";
        $d = dir($path);

        $listos = array();
        while (false !== ($entry = $d->read())) {
            $full = $path . $entry;
            if (is_file($full) and substr($full, -3) == "php") {
                $listos[] = $entry;
            }
        }
        $d->close();

        $count = count($listos);

        $body = '<?php ' . "\n";
        $body.= "/* Atualizado por Ally em " . date("Y-m-d H:i:s") . " pela última vez */ \n\n";
        $body.= "\n\nclass CI_Model {\n";

        foreach ($listos as $row) {
            $valor = substr($row, 0, -4);
            $body.= "
\t/**
\t* @var $valor
\t*/
\tpublic \${$valor}; \n";
        }

        $body.= "}";


        $body.= "\n\nclass CI_Controller {\n";
        foreach ($listos as $row) {
            $valor = substr($row, 0, -4);
            $body.= "
\t/**
\t* @var $valor
\t*/
\tpublic \${$valor}; \n";
        }
        $body.= "}";


        $pathFile = $this->config->item("local_disk_url") . "fake_autocomplete_gerado.php";
        $file = fopen($pathFile, "w+");


        if (fwrite($file, $body)) {
            fclose($file);

            echo ColorCLI::getColoredString("\n$count models (fiu fiu beleza heim!?)... Prontinho chefe!\n", 'white');
            sleep(2);
            $this->index();
        }
    }

    /**
     * Tela inicial após no menu principal escolher que quer gerar um controller
     */
    private function initGerarController() {

        echo ColorCLI::getColoredString("\n----> Gerar CONTROLLER do Manager \n");
        echo ColorCLI::getColoredString("\nPara qual model deseja criar o Controller?\n\n");

        $path = $this->config->item("local_disk_url") . "application/models/";
        $d = dir($path);

        $listos = array();
        while (false !== ($entry = $d->read())) {
            $full = $path . $entry;
            if (is_file($full) and substr($full, -3) == "php") {
                $nomeModel = substr($entry, 0, -4);
                ;

                if (!in_array($nomeModel, $this->preventOverwriteModels)) {
                    $listos[] = $nomeModel;
                }
            }
        }

        $d->close();

        $break = 0;
        foreach ($listos as $key => $exibir) {
            $break++;

            // 40 caracteres cada coluna, simula tipo um tab, completa com espaço para ficar alinhado
            $exibir = str_pad($key . " => " . $exibir, 40);

            echo ColorCLI::getColoredString($exibir, 'white');

            if ($break == $this->qtdColunasUsar) { // quantas colunas, em que deve quebrar linha.
                echo("\n");
                $break = 0;
            }
        }

        echo ColorCLI::getColoredString("\n\nInforme um número como '3', uma lista como '3,5,8' ou intervalo como '8-12'.\nInforme 'b' para voltar. Decida :  ", "white");
        $answer = clir();

        if ($answer == 'b')
            $this->index();

        // escolheu um item individual
        if (isset($listos[$answer])) {
            $this->preCheckCriateController($listos[$answer]);
        }


        // escolheu um intervalo
        $porRange = explode("-", $answer);
        if (count($porRange) == 2):
            for ($magaiver = $porRange[0]; $magaiver <= $porRange[1]; $magaiver++):
                if (isset($listos[$magaiver])) :
                    $this->preCheckCriateController($listos[$magaiver]);
                endif;
            endfor;
        endif;


        // escolheu uma lista
        $porVirgula = explode(',', $answer);
        if (count($porVirgula) > 1):
            foreach ($porVirgula as $ichooseYou):
                if (isset($listos[$ichooseYou])):
                    $this->preCheckCriateController($listos[$ichooseYou]);
                endif;
            endforeach;
        endif;


        // ao fim de gerar os controllers.. volta para o inicio deles
        $this->initGerarController();
    }

    /**
     * Faz as verificações que devem ser feitas antes do controller 
     * ser gerado.
     * 
     * @param type $controller
     */
    public function preCheckCriateController($controller) {

        $iniName = "m_" . substr($controller, 3);

        if (in_array($iniName, $this->preventOverwriteControllersManager)) {
            echo ColorCLI::getColoredString("\nO controller do manager $iniName não pode ser gerado automáticamente. Nada feito.\n", 'red');
            echo ColorCLI::getColoredString("\nAguardando 5 segundos para voltar ...\n");
            sleep(5);
            $this->initGerarController();
        }


        $emDisco = $iniName . ".php";
        $path = $this->config->item("local_disk_url") . "application/controllers/manager/" . $emDisco;


        if (file_exists($path)) {

            echo ColorCLI::getColoredString("\nO arquivo já existe! Primeiras {$this->linhasPreview} linhas abaixo:\n", 'red');
            $this->filePreview($path);

            echo ColorCLI::getColoredString("\nSobrescrever o arquivo (s/n)? ");
            $answer = clir();

            if ($answer == 's') {
                $this->genController($controller);
            } else {
                $this->initGerarController();
            }
        } else {
            // nao existe! Mete fixa!
            $this->genController($controller);
        }
    }

    /**
     * Gera em disco o arquivo para este controller
     * 
     * @param type $controller
     */
    public function genController($controller) {

        $emDisco = "m_" . substr($controller, 3) . ".php";
        $path = $this->config->item("local_disk_url") . "application/controllers/manager/" . $emDisco;


        $className = "m_" . substr($controller, 3);
        $urlName = substr($controller, 3);
        $ModelCorrespondente = "nt_" . substr($controller, 3);


        $controllerTemplate = "<?php

/**
 * Description of m_favicon
 *
 * @author Seu Nome Aqui <seu-email@noiatec.com.br>, <seu-outro-email@pessoal.com>
 */
class $className extends NT_Manager_Controller {

    private \$crud;

    public function __construct() {
        parent::__construct();

        \$this->checkLogin();

        \$this->load->library('grocery_CRUD');

        \$this->crud = new grocery_CRUD();

        \$this->load->model('{$ModelCorrespondente}');

        \$this->crud->set_rules(\$this->{$ModelCorrespondente}->getRules())
                ->auto_label(\$this->{$ModelCorrespondente}->getRules())
                ->set_table(\$this->{$ModelCorrespondente}->getSft())
                ->unset_print();

        if (!\$this->nt_manager_permissoes->isValid(array('manager', '{$urlName}', 'index', 'export')))
            \$this->crud->unset_export();

        if (!\$this->nt_manager_permissoes->isValid(array('manager', '{$urlName}', 'index', 'add')))
            \$this->crud->unset_add();

        if (!\$this->nt_manager_permissoes->isValid(array('manager', '{$urlName}', 'index', 'edit')))
            \$this->crud->unset_edit();

        if (!\$this->nt_manager_permissoes->isValid(array('manager','{$urlName}', 'index', 'delete')))
            \$this->crud->unset_delete();
    }

    public function index() {

        \$crud = \$this->crud->render();
        \$data['crud'] = \$crud;
        \$this->load->view('manager/m_default/index', \$data);
    }

}";


        // grava em disco efetivamente
        $fhandle = fopen($path, 'w+');
        fwrite($fhandle, $controllerTemplate);
        fclose($fhandle);

        echo ColorCLI::getColoredString("\nCriado $emDisco \n");
        sleep(1);
    }

    public function iniSvnExport() {

        if(!function_exists ("svn_status")){
            echo("\nErro: Sem módulo php_svn em sua maquina. Nada feito.\n");
            sleep(5);
            $this->index();
        }
        
        $repoUrl = $this->config->item("local_disk_url");
        
        echo("\n");

        echo("\nSeu usuário de svn: ");
        $user = clir();
        echo("\nSua senha de svn: ");
        $pass = clir();
        echo("....\n");

        svn_auth_set_parameter(SVN_AUTH_PARAM_DEFAULT_USERNAME, $user);
        svn_auth_set_parameter(SVN_AUTH_PARAM_DEFAULT_PASSWORD, $pass);

        $dados = svn_status($repoUrl,SVN_ALL);
        $dados = (isset($dados[0])?$dados[0]:false);

        if (!isset($dados['revision'])) {
            echo ("\nNão consigo ler as conf's de svn do projeto... faça manualmente\n");
            sleep(2);
            $this->index();
        }

        echo("--------------- Sua cópia de trabalho ----------------------------\n\n");
        echo("Versão desta cópia de trabalho: " . $dados['revision']);
        echo("\n*Antes de exportar, certifique-se que esteja atualisada, ou que é mesmo esta versão que deseja publicar.\n\n");
        
        echo("---------------- site-version.txt  -------------------------------\n\n");
        $site = file_get_contents($repoUrl . "site-version.txt");
        echo $site;

        // apenas para sugerir ao usuário uma versão mais próxima do que de verdade pode ser
        $hipoteticaVersao = $dados['revision'] - rand(5, 15);


        echo("\n\n----------------------------------------------------------------\n\n");
        
        echo("Você pode exportar todo o site informando o nro da versão a exportar, ex.: '{$dados['revision']}';\n");
        echo("Se quiser exportar apenas a diferença entre versões informe um intervalo\n");
        echo("como: $hipoteticaVersao-{$dados['revision']}.\n");



        $txtSitePartes = explode('"', $site);


        $dataVelha = $txtSitePartes[1];
        $dataNova = date("Y-m-d H:i:s");

        $versaoVelha = $txtSitePartes[3];
        $versaoNova = $dados['revision'];

        $novoTexto = str_replace('"' . $dataVelha . '"', '"' . $dataNova . '"', $site);
        $novoTexto = str_replace('"' . $versaoVelha . '"', '"' . $versaoNova . '"', $novoTexto);

        //echo($novoTexto);

        echo("\n\nExportar ('b' para voltar) : ");
        $exportar = clir();
        if($exportar == 'b'){
            $this->index();
        }
        
        
        echo("\nModificando e commitando o site-version.txt ...\n");
        $handle = fopen($repoUrl . "site-version.txt","w+");
        fwrite($handle, $novoTexto);
        fclose($handle);
        svn_commit("Ally atualizou versao para {$versaoNova}", array($repoUrl . "site-version.txt"));
        
        echo("\nLendo logs ...\n");

        // o usuario informou um nro, então vai do base até tal revisão
        $partes = explode("-", $exportar);

        if (count($partes) == 1) {
            $base = intval(1);
            $top = intval($exportar);

            $log = svn_log($repoUrl, $exportar, $base, $top);
        } elseif (count($partes) == 2) { // 100-105
            $base = intval($partes[0]);
            $top = intval($partes[1]);

            $log = svn_log($repoUrl, $base, $top);
        }

        // Onde vou criar o diretorio com os aruivos exportados
        $homeUnix = getenv("HOME"); // Linux, Unix, Mac
        $homeWindows = getenv("HOMEPATH"); // O bobo

        $usePath = "";
        if (strlen($homeUnix) != 0) {
            $usePath = $homeUnix;
        } else {
            $usePath = $homeWindows;
        }

        $usePath.= "/svn-export-" . date("Y-m-d_H.m.s") . "-version_base-" . $base . "-to-" . $top . "/";
        mkdir($usePath);
        echo("Resutado em: $usePath \n\n");
        
        $zip = new ZipArchive();
        $filename = $usePath."dump_files.zip";
        if ($zip->open($filename, ZipArchive::CREATE)!==TRUE) {
            exit("cannot open <$filename>\n");
        }        

        $DeleteList = "";
        $ChangedList = "";
        $changedOnlyFiles = "";
        foreach ($log as $row):
            foreach ($row['paths'] as $changes):

                // Modified or Added file
                if ($changes['action'] == 'M' or $changes['action'] == 'A') {

                    $origemPar1 = substr($repoUrl, 0, -1);
                    $origemPart2 = $changes['path'];

                    $exO1 = explode("/", $origemPar1);
                    $exO2 = explode("/", $origemPart2);

                    unset($exO1[0], $exO2[0]);

                    if ($exO1[count($exO1)] == $exO2[1]) {
                        $origemPart2 = str_replace($exO2[1] . "/", "", $origemPart2);
                    }


                    $fullThisFile = $origemPar1 . $origemPart2;

                    $fullDestination = substr($usePath, 0, -1) . $changes['path'];


                    // um arquivo de uma versao antiga pode nao existir mais na origem, vir a ser
                    // deletado depois, portanto nao tem como copialo
                    if(file_exists($fullThisFile) and is_file($fullThisFile)){
                        $ChangedList.= "Rev {$row['rev']} changed {$changes['path']}\n";
                        $changedOnlyFiles.= "{$changes['path']}\n";
                        
                        $zip->addFromString($fullThisFile, file_get_contents($fullThisFile));
                    }
                }

                // deleted File
                if ($changes['action'] == 'D') {
                    $DeleteList.= "Rev {$row['rev']} deletado {$changes['path']}\n";
                }

            endforeach; // dos arquivos desta revisao
        endforeach; // das revisoes

        $fDeletados = fopen($usePath . "svn-deleted-files.txt", "w+");
        fwrite($fDeletados, $DeleteList);
        fclose($fDeletados);

        $fChanged = fopen($usePath . "svn-changed-files.txt", "w+");
        fwrite($fChanged, $ChangedList);
        fclose($fChanged);
        
        
        // add o novo seite version junto no pacote
        $zip->addFromString($repoUrl . "site-version.txt", file_get_contents($repoUrl . "site-version.txt"));
        
        $zip->close();
        
        echo("\nFeito! Enter para voltar ao início.\n");
        clir();
        $this->index();        
    }
    
}
